/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;

namespace Xceed.Silverlight.Data
{
  internal class LocalGroupDataRangeEnumerator : DataRangeEnumeratorBase
  {
    #region Static Fields

    private static readonly Guid GetAllRangeCallId = DataUtils.CreateCallId();

    #endregion


    internal LocalGroupDataRangeEnumerator(
      IEnumerable<DataRange> ranges,
      IEnumerable<GroupDescription> groupDescriptions,
      IEnumerable<SortDescription> sortDescriptions,
      IEnumerable<SortDescription> primaryKeys,
      Type userObjectType,
      Func<GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> dataSourceEnumeratorCreator,
      Func<DataSourceProvider, GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> intermediateEnumeratorCreator )
    {
      if( dataSourceEnumeratorCreator == null )
        throw new ArgumentNullException( "dataSourceEnumeratorCreator" );

      if( intermediateEnumeratorCreator == null )
        throw new ArgumentNullException( "intermediateEnumeratorCreator" );

      m_groupDescriptions = groupDescriptions;
      m_sortDescriptions = sortDescriptions;
      m_primaryKeys = primaryKeys;
      m_userObjectType = userObjectType;
      m_enumeratorCreator = intermediateEnumeratorCreator;
      m_sourceEnumerator = new DataRangeEnumerator( ranges, dataSourceEnumeratorCreator );
      m_DataRanges = ranges;
      m_DataSourceEnumeratorCreator = dataSourceEnumeratorCreator;
    }


    #region HasMore Property

    protected override bool HasMore
    {
      get
      {
        return ( !m_disposed ) && ( m_hasMore );
      }
    }

    #endregion


    public override void Reset()
    {
      this.CheckState();

      // Get back at start.
      m_hasMore = true;

      if( m_groupedEnumerator != null )
      {
        m_groupedEnumerator.Reset();
      }

      if( m_sourceEnumerator != null )
      {
        m_sourceEnumerator.Reset();
      }
    }

    protected override void Dispose( bool disposing )
    {
      base.Dispose( disposing );

      // Release the source enumerator.
      if( m_sourceEnumerator != null )
      {
        m_sourceEnumerator.Dispose();
        m_sourceEnumerator = null;
      }

      // Release the grouped enumerator.
      if( m_groupedEnumerator != null )
      {
        m_groupedEnumerator.Dispose();
        m_groupedEnumerator = null;
      }

      m_enumeratorCreator = null;
      m_groupDescriptions = null;
      m_sortDescriptions = null;
      m_primaryKeys = null;

      m_hasMore = false;
      m_disposed = true;
    }

    protected override IAsyncResult BeginGetNextCore( AsyncCallback callback, object state )
    {
      // The data in the ranges have been all fetched.  We may now return them
      // grouped and sorted.
      if( m_groupedEnumerator != null )
      {
        return m_groupedEnumerator.BeginGetNext( callback, state );
      }
      // In order to group and sort the data, we need to fetch all the ranges first.
      else
      {
        return this.GetAllRange( callback, state );
      }
    }

    protected override GetNextResult EndGetNextCore( IAsyncResult asyncResult )
    {
      // Simulate the end of the source if the enumerator has been disposed.
      if( m_disposed )
        return new GetNextResult();

      // We are enumerating the local result.
      GetNextResult result;
      if( !asyncResult.UseCallId( LocalGroupDataRangeEnumerator.GetAllRangeCallId ) )
      {
        result = m_groupedEnumerator.EndGetNext( asyncResult );
        if( result == null )
        {
          result = new GetNextResult();
        }

        m_hasMore = result.Success;
      }
      // We have fetched all the desired data ranges on the data source.
      else
      {
        IEnumerable userObjects = asyncResult.GetCallResult<IEnumerable>();
        DataSourceProvider dataSource = new ListDataSourceProvider( userObjects );
        dataSource.Init();

        m_groupedEnumerator = this.CreateGroupedDataRangeEnumerator( dataSource );
        m_groupedEnumerator.Reset();

        // We must return the first item.
        IAsyncResult syncResult = m_groupedEnumerator.BeginGetNext( null, null );

        // The result should be returned synchronously since we're using a SyncDataSourceProvider.
        Debug.Assert( syncResult.CompletedSynchronously );

        result = m_groupedEnumerator.EndGetNext( syncResult );
      }

      return result;
    }

    private IAsyncResult GetAllRange( AsyncCallback callback, object state )
    {
      AsyncResult asyncResult = new AsyncResult( 
                                  LocalGroupDataRangeEnumerator.GetAllRangeCallId, 
                                  callback, 
                                  state );
      List<DataPath> dataPaths = new List<DataPath>();

      Func<AsyncCallback, object, IAsyncResult> beginGetNextItem = null;
      Func<AsyncResult, IAsyncResult, DependentCallResult> endGetNextItem = null;

      int currentRangeID = 0;
      m_sourceEnumerator = new DataRangeEnumerator( new DataRange[] { m_DataRanges.ElementAt( currentRangeID ) }, m_DataSourceEnumeratorCreator );

      beginGetNextItem = ( cb, st ) => m_sourceEnumerator.BeginGetNext( cb, st );

      endGetNextItem = ( par, iar ) =>
      {
        if( m_disposed )
          return new DependentCallResult( null );

        // Fetch the next data item.
        GetNextResult result = m_sourceEnumerator.EndGetNext( iar );
        if( result.Success )
        {
          // When using a remote data source, we should always receive SelectionRanges
          if( result.IsInInclusiveRange )
          {
            if( !dataPaths.Contains( result.Item ) )
            {
              dataPaths.Add( result.Item );
            }
          }

          return new DependentCallResult( null, beginGetNextItem, endGetNextItem );
        }
        // We have all the desired items from current range.
        else
        {
          while( true )
          {
            if( ++currentRangeID == m_DataRanges.Count() )
            {
              // We won't need the enumerator on the data source anymore.
              m_sourceEnumerator.Dispose();
              m_sourceEnumerator = null;

              IEnumerable userObjects = LocalGroupDataRangeEnumerator.GetUserObjects( dataPaths );
              userObjects = this.GetTypedUserObjects( userObjects );

              return new DependentCallResult( userObjects );
            }
            else
            {
              if( m_DataRanges.ElementAt( currentRangeID ).IsSelectionRangeInclusive )
              {
                m_sourceEnumerator.Dispose();
                m_sourceEnumerator = new DataRangeEnumerator( new DataRange[]{m_DataRanges.ElementAt( currentRangeID )}, m_DataSourceEnumeratorCreator );

                beginGetNextItem = ( cb, st ) => m_sourceEnumerator.BeginGetNext( cb, st );
                return new DependentCallResult( null, beginGetNextItem, endGetNextItem );
              }             

              RemoveUnSelectedDataPaths( m_DataRanges.ElementAt( currentRangeID ), dataPaths );
            }
          }
        }
      };

      asyncResult.Start( beginGetNextItem, endGetNextItem );
      return asyncResult;
    }

    private void RemoveUnSelectedDataPaths( DataRange currentDataRange, List<DataPath> dataPaths )
    {
      List<DataPath> unSelectedDataPaths = new List<DataPath>();

      foreach( DataPath datapath in dataPaths )
      {
        if( ( currentDataRange != null ) && currentDataRange.Contains( datapath ) )
        {
          unSelectedDataPaths.Add( datapath );
        }
      }
      foreach( DataPath unSelectedDataPath in unSelectedDataPaths )
      {
        dataPaths.Remove( unSelectedDataPath );
      }
    }

    private IDataRangeEnumerator CreateGroupedDataRangeEnumerator( DataSourceProvider dataSource )
    {
      Func<GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> enumeratorCreator =
        ( groupHandler, sortHandler, filterHandler ) =>
          m_enumeratorCreator.Invoke( dataSource, groupHandler, sortHandler, filterHandler );

      // Create a range that will return the whole source.
      DataRangeContext dataRangeContext = new DataRangeContext(
                                            dataSource.ElementType,
                                            dataSource.PrimaryKeyInfo,
                                            null,
                                            null );
      DataRange dataRange = DataRange.All( true, dataRangeContext );

      return new GroupDataRangeEnumerator(
        new DataRange[] { dataRange },
        m_groupDescriptions,
        m_sortDescriptions,
        dataSource.PrimaryKeyInfo,
        dataSource.ElementType,
        dataSource.GetElementMemberType,
        enumeratorCreator );
    }

    private IEnumerable GetTypedUserObjects( IEnumerable userObjects )
    {
      if( m_userObjectType != typeof( object ) )
      {
        MethodInfo methodInfo = typeof( Enumerable ).GetMethod( "Cast" );
        if( ( methodInfo != null )
          && ( methodInfo.IsGenericMethod && methodInfo.GetGenericArguments().Length == 1 )
          && ( methodInfo.GetParameters().Length == 1 ) )
        {
          methodInfo = methodInfo.MakeGenericMethod( m_userObjectType );
          userObjects = ( IEnumerable )methodInfo.Invoke( null, new object[] { userObjects } );
        }
      }

      return userObjects;
    }

    private static IEnumerable GetUserObjects( IEnumerable<DataPath> dataPaths )
    {
      if( dataPaths == null )
        yield break;

      foreach( DataPath dataPath in dataPaths )
      {
        RawDataItem dataItem = dataPath.LastChild as RawDataItem;
        if( dataItem != null )
        {
          yield return dataItem.Data;
        }
      }
    }


    #region Private Fields

    private IDataRangeEnumerator m_sourceEnumerator;
    private IDataRangeEnumerator m_groupedEnumerator; //null
    private IEnumerable<GroupDescription> m_groupDescriptions;
    private IEnumerable<SortDescription> m_sortDescriptions;
    private IEnumerable<SortDescription> m_primaryKeys;
    private Type m_userObjectType;
    private Func<DataSourceProvider, GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> m_enumeratorCreator;
    private bool m_hasMore = true;
    private bool m_disposed; //false
    private IEnumerable<DataRange> m_DataRanges;
    private Func<GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> m_DataSourceEnumeratorCreator;

    #endregion
  }
}
